package sebastiankerckhof.helper.sampler;

import java.util.ArrayList;
import java.util.List;

import sebastiankerckhof.math.Point2f;
import sebastiankerckhof.math.Point3f;

public abstract class AbstractSampler implements Sampler{
	protected int num_samples;        // the number of sample points in a pattern
	protected int num_sets;   // the number of sample sets (patterns) stored
	protected List<Point2f> samples; //sample points on a unit square
	protected List<Point2f> disk_samples; //sample points on a unit disk
	protected List<Point3f> hemisphere_samples; //sample points on a hemisphere disk
	protected List<Point3f> sphere_samples;                 // sample points on a unit sphere
	protected List<Integer> shuffled_indices;     // shuffled samples array indices
	protected long count;    //current number of sample points used
	protected int jump;       //random index jump
	
	public AbstractSampler(int samples, int sets, int count, int jump){
		this.num_samples = samples;
		this.num_sets = sets;
		this.count = count;
		this.jump = jump;
		
		this.samples = new ArrayList<Point2f>(num_samples*num_sets);
		setup_shuffled_indices();
	}
	
	public AbstractSampler(){
		this(1,83,0,0);
	}
	
	public AbstractSampler(int num){
		this(num, 83, 0, 0);
	}

	public AbstractSampler(int samples, int sets){
		this(samples, sets, 0, 0);
	}
	
	

  @Override
  public Point2f sample_unit_square() {
          if (count % num_samples == 0)   // start of new pixel
          {
                  jump = (Math.rand % num_sets) * num_samples;
          }
  
          return (samples.get(jump + shuffled_indices.get((int) (jump + count++ % num_samples))));
  }

  @Override
  public Point2f sample_unit_disk(){
    if (count % num_samples == 0)   // start of new pixel
    {
            jump = (rand_int() % num_sets) * num_samples;
    }

    return (disk_samples.get(jump + shuffled_indices.get((int) (jump + count++ % num_samples))));
  }

  @Override
  public Point3f sample_hemisphere(){
    if (count % num_samples == 0)   // start of new pixel
    {
            jump = (rand_int() % num_sets) * num_samples;
    }

    return (hemisphere_samples.get(jump + shuffled_indices.get((int) (jump + count++ % num_samples))));
  }

  @Override
  public void setup_shuffled_indices(){
  	 shuffled_indices = new ArrayList<Integer>(num_samples * num_sets);
     int[] indices = new int[num_samples];

     for (int j = 0; j < num_samples; j++)
             indices[j] = j;

     for (int p = 0; p < num_sets; p++) {
             //random_shuffle(indices.begin(), indices.end());
             
            
             int n = (indices[indices.length]-indices[0]);
             for (int i=n-1; i>0; --i){ 
            	 int temp = indices[i];
            	 int rand = 
            	 swap (first[i],first[rand(i+1)]);
             }
             
             for (int j = 0; j < num_samples; j++)
                     shuffled_indices.add(indices[j]);
     }
  }

  @Override
  public void map_samples_to_unit_disk(){
  	int size = samples.size();
    float r, phi;   //polar coordinates
    Point2f sp = new Point2f();     // sample point on unit disk

    disk_samples = new ArrayList<Point2f>(size);

    for (int j = 0; j < size; j++) {
            // map sample point to [-1, 1]

            sp.x = 2.0f * samples.get(j).x - 1.0f;
            sp.y = 2.0f * samples.get(j).y - 1.0f;

            if (sp.x > -sp.y) {                                             // sectors 1 and 2
                    if (sp.x > sp.y) {                                      // sector 1
                            r = sp.x;
                            phi = sp.y / sp.x;
                    }
                    else {                                                          // sector 2
                            r = sp.y;
                            phi = 2 - sp.x / sp.y;
                    }
            }
            else {                                                                  //sectors 3 and 4
                    if (sp.x < sp.y) {                                      // sector 3
                            r = -sp.x;
                            phi = 4f + sp.y / sp.x;
                    }
                    else {                                                          // sector 4
                            r = -sp.y;
                            if (sp.y != 0.0)                                // BAD! / by 0 at origin
                                    phi = 6f - sp.x / sp.y;
                            else
                                    phi = 0.0f;
                    }
            }

            phi *= Math.PI / 4.0f;                // common to all sectors

            disk_samples.set(j, new Point2f(r * (float) Math.cos(phi), r * (float) Math.sin(phi)));
            disk_samples.get(j).x = r * (float) Math.cos(phi);
            disk_samples.get(j).y = r * (float) Math.sin(phi);
    }
  }

  @Override
  public void map_samples_to_hemisphere(float e){
  	 int size = samples.size();
     hemisphere_samples = new ArrayList<Point3f>(num_samples * num_sets);

     for (int j = 0; j < size; j++) {
             float cos_phi = (float) Math.cos(2.0f * Math.PI * samples.get(j).x);
             float sin_phi = (float) Math.sin(2.0f * Math.PI * samples.get(j).x);
             float cos_theta = (float) Math.pow((1.0f - samples.get(j).y), 1.0f / (e + 1.0f));
             float sin_theta = (float) Math.sqrt(1.0f - cos_theta * cos_theta);
             float pu = sin_theta * cos_phi;
             float pv = sin_theta * sin_phi;
             float pw = cos_theta;

             hemisphere_samples.add(new Point3f(pu, pv, pw));
     }
  }

  public void map_samples_to_sphere(){
  	 float r1, r2;
     float x, y, z;
     float r, phi;

     sphere_samples = new ArrayList<Point3f>(num_samples * num_sets);

     for (int j = 0; j < num_samples * num_sets; j++) {
             r1      = samples.get(j).x;
             r2      = (float) samples.get(j).y;
             z       = (float) 1.0f - 2.0f * r1;
             r       = (float) Math.sqrt(1.0 - z * z);
             phi 		 = (float) Math.PI * 2 * r2; //TODO check this 
             x       = r * (float) Math.cos(phi);
             y       = r * (float) Math.sin(phi);
             sphere_samples.add(new Point3f(x, y, z));
     }
  }

  public Point3f sample_sphere(){
    if (count % num_samples == 0)                                                                   // start of a new pixel
      jump = (rand_int() % num_sets) * num_samples;

    return (sphere_samples.get(jump + shuffled_indices.get(jump + count++ % num_samples)));
  }


	

}
